Coverage Report

Created: 2025-11-02 11:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\csshw\csshw\src\utils\windows.rs
Line
Count
Source
1
//! Windows API abstraction layer for console and system operations.
2
//!
3
//! This module provides a trait-based abstraction over Windows APIs to enable
4
//! mocking in tests and centralize Windows-specific functionality.
5
6
#![deny(clippy::implicit_return)]
7
#![allow(
8
    clippy::needless_return,
9
    clippy::doc_overindented_list_items,
10
    rustdoc::private_intra_doc_links
11
)]
12
13
use log::error;
14
use std::ffi::OsString;
15
use std::os::windows::ffi::OsStrExt;
16
use std::{mem, ptr};
17
18
use windows::core::{HSTRING, PCWSTR};
19
use windows::Win32::Foundation::{BOOL, COLORREF, FALSE, HANDLE, HWND, LPARAM, TRUE};
20
use windows::Win32::Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_BORDER_COLOR};
21
use windows::Win32::System::Com::{CoCreateInstance, CLSCTX_ALL};
22
use windows::Win32::System::Console::{
23
    FillConsoleOutputAttribute, GetConsoleScreenBufferInfo, GetConsoleWindow, GetStdHandle,
24
    ReadConsoleInputW, SetConsoleTextAttribute, CONSOLE_CHARACTER_ATTRIBUTES,
25
    CONSOLE_SCREEN_BUFFER_INFO, COORD, INPUT_RECORD, INPUT_RECORD_0, STD_HANDLE, STD_INPUT_HANDLE,
26
    STD_OUTPUT_HANDLE,
27
};
28
use windows::Win32::System::Console::{GetConsoleMode, SetConsoleMode, CONSOLE_MODE};
29
use windows::Win32::System::Console::{
30
    ScrollConsoleScreenBufferW, SetConsoleCursorPosition, CHAR_INFO, KEY_EVENT as KEY_EVENT_U32,
31
    SMALL_RECT,
32
};
33
use windows::Win32::System::Threading::PROCESS_ACCESS_RIGHTS;
34
use windows::Win32::System::Threading::{
35
    CreateProcessW, CREATE_NEW_CONSOLE, PROCESS_INFORMATION, STARTUPINFOW,
36
};
37
use windows::Win32::System::Threading::{GetExitCodeProcess, OpenProcess};
38
use windows::Win32::UI::Accessibility::{CUIAutomation, IUIAutomation};
39
use windows::Win32::UI::WindowsAndMessaging::{
40
    EnumWindows, GetWindowTextW, GetWindowThreadProcessId, MoveWindow, SetWindowTextW,
41
    SYSTEM_METRICS_INDEX,
42
};
43
use windows::Win32::UI::WindowsAndMessaging::{
44
    GetForegroundWindow, GetWindowPlacement, SetForegroundWindow, ShowWindow, SHOW_WINDOW_CMD,
45
    WINDOWPLACEMENT,
46
};
47
48
#[cfg(test)]
49
use mockall::automock;
50
51
use super::constants::MAX_WINDOW_TITLE_LENGTH;
52
53
/// Trait for Windows API operations to enable mocking in tests.
54
///
55
/// This trait abstracts Windows API calls to allow for unit testing without
56
/// actual system interaction. All console and system operations should go
57
/// through this trait.
58
#[cfg_attr(test, automock)]
59
pub trait WindowsApi: Send + Sync {
60
    /// Sets the console window title.
61
    ///
62
    /// # Arguments
63
    ///
64
    /// * `title` - The string to be set as window title
65
    ///
66
    /// # Returns
67
    ///
68
    /// Result indicating success or failure of the operation
69
    fn set_console_title(&self, title: &str) -> windows::core::Result<()>;
70
71
    /// Gets the console window title as UTF-16 buffer.
72
    ///
73
    /// # Arguments
74
    ///
75
    /// * `buffer` - Mutable buffer to store the UTF-16 encoded title
76
    ///
77
    /// # Returns
78
    ///
79
    /// Number of characters copied to the buffer
80
    fn get_console_title(&self, buffer: &mut [u16]) -> i32;
81
82
    /// Gets OS version string.
83
    ///
84
    /// # Returns
85
    ///
86
    /// String representation of the OS version
87
    fn get_os_version(&self) -> String;
88
89
    /// Arranges the console window position and size.
90
    ///
91
    /// # Arguments
92
    ///
93
    /// * `x` - The x coordinate to move the window to
94
    /// * `y` - The y coordinate to move the window to
95
    /// * `width` - The width in pixels to resize the window to
96
    /// * `height` - The height in pixels to resize the window to
97
    ///
98
    /// # Returns
99
    ///
100
    /// Result indicating success or failure of the operation
101
    fn arrange_console(&self, x: i32, y: i32, width: i32, height: i32)
102
        -> windows::core::Result<()>;
103
104
    /// Sets console text attribute.
105
    ///
106
    /// # Arguments
107
    ///
108
    /// * `attributes` - Console character attributes to set
109
    ///
110
    /// # Returns
111
    ///
112
    /// Result indicating success or failure of the operation
113
    fn set_console_text_attribute(
114
        &self,
115
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
116
    ) -> windows::core::Result<()>;
117
118
    /// Gets console screen buffer info.
119
    ///
120
    /// # Returns
121
    ///
122
    /// Console screen buffer information or error
123
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO>;
124
125
    /// Fills console output with specified attribute.
126
    ///
127
    /// # Arguments
128
    ///
129
    /// * `attribute` - Attribute to fill with
130
    /// * `length` - Number of characters to fill
131
    /// * `coord` - Starting coordinate
132
    ///
133
    /// # Returns
134
    ///
135
    /// Number of characters actually filled or error
136
    fn fill_console_output_attribute(
137
        &self,
138
        attribute: u16,
139
        length: u32,
140
        coord: COORD,
141
    ) -> windows::core::Result<u32>;
142
143
    /// Scrolls console screen buffer.
144
    ///
145
    /// # Arguments
146
    ///
147
    /// * `scroll_rect` - Rectangle to scroll
148
    /// * `scroll_target` - Target coordinate for scrolling
149
    /// * `fill_char` - Character to fill empty space with
150
    ///
151
    /// # Returns
152
    ///
153
    /// Result indicating success or failure of the operation
154
    fn scroll_console_screen_buffer(
155
        &self,
156
        scroll_rect: SMALL_RECT,
157
        scroll_target: COORD,
158
        fill_char: CHAR_INFO,
159
    ) -> windows::core::Result<()>;
160
161
    /// Sets console cursor position.
162
    ///
163
    /// # Arguments
164
    ///
165
    /// * `position` - New cursor position
166
    ///
167
    /// # Returns
168
    ///
169
    /// Result indicating success or failure of the operation
170
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()>;
171
172
    /// Gets standard handle.
173
    ///
174
    /// # Arguments
175
    ///
176
    /// * `handle_type` - Type of standard handle to retrieve
177
    ///
178
    /// # Returns
179
    ///
180
    /// Handle to the requested standard device or error
181
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE>;
182
183
    /// Reads console input.
184
    ///
185
    /// # Arguments
186
    ///
187
    /// * `buffer` - Buffer to store input records
188
    ///
189
    /// # Returns
190
    ///
191
    /// Number of records read or error
192
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32>;
193
194
    /// Sets DWM window attribute for border color.
195
    ///
196
    /// # Arguments
197
    ///
198
    /// * `color` - Color to set as border color
199
    ///
200
    /// # Returns
201
    ///
202
    /// Result indicating success or failure of the operation
203
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()>;
204
205
    /// Writes input records to the console input buffer.
206
    ///
207
    /// # Arguments
208
    ///
209
    /// * `buffer` - Input records to write
210
    /// * `number_written` - Mutable reference to store number of records written
211
    ///
212
    /// # Returns
213
    ///
214
    /// Result indicating success or failure of the operation
215
    fn write_console_input(
216
        &self,
217
        buffer: &[INPUT_RECORD],
218
        number_written: &mut u32,
219
    ) -> windows::core::Result<()>;
220
221
    /// Gets the last Windows error code.
222
    ///
223
    /// # Returns
224
    ///
225
    /// The last error code from Windows API
226
    fn get_last_error(&self) -> u32;
227
228
    /// Generates a console control event.
229
    ///
230
    /// # Arguments
231
    ///
232
    /// * `ctrl_event` - Control event type to generate
233
    /// * `process_group_id` - Process group ID to send event to
234
    ///
235
    /// # Returns
236
    ///
237
    /// Result indicating success or failure of the operation
238
    fn generate_console_ctrl_event(
239
        &self,
240
        ctrl_event: u32,
241
        process_group_id: u32,
242
    ) -> windows::core::Result<()>;
243
244
    /// Get standard output handle
245
    ///
246
    /// # Returns
247
    ///
248
    /// Handle to standard output or error
249
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE>;
250
251
    /// Get console screen buffer information
252
    ///
253
    /// # Arguments
254
    ///
255
    /// * `handle` - Handle to console screen buffer
256
    ///
257
    /// # Returns
258
    ///
259
    /// Console screen buffer information or error
260
    fn get_console_screen_buffer_info_with_handle(
261
        &self,
262
        handle: HANDLE,
263
    ) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO>;
264
265
    /// Create a new process
266
    ///
267
    /// # Arguments
268
    ///
269
    /// * `application` - Application name including file extension
270
    /// * `args` - List of arguments to the application
271
    ///
272
    /// # Returns
273
    ///
274
    /// Process information if successful, None otherwise
275
0
    fn create_process_with_args(
276
0
        &self,
277
0
        application: &str,
278
0
        args: Vec<String>,
279
0
    ) -> Option<windows::Win32::System::Threading::PROCESS_INFORMATION> {
280
0
        let command_line = build_command_line(application, &args);
281
0
        let mut startupinfo = STARTUPINFOW {
282
0
            cb: mem::size_of::<STARTUPINFOW>() as u32,
283
0
            ..Default::default()
284
0
        };
285
0
        let mut process_information = PROCESS_INFORMATION::default();
286
0
        let mut cmd_line = command_line;
287
0
        let command_line_ptr = windows::core::PWSTR(cmd_line.as_mut_ptr());
288
289
0
        match self.create_process_raw(
290
0
            application,
291
0
            command_line_ptr,
292
0
            &mut startupinfo,
293
0
            &mut process_information,
294
0
        ) {
295
0
            Ok(()) => return Some(process_information),
296
0
            Err(_) => return None,
297
        }
298
0
    }
299
300
    /// Low-level process creation API call
301
    ///
302
    /// # Arguments
303
    ///
304
    /// * `application` - Application name
305
    /// * `command_line` - Command line string as PWSTR
306
    /// * `startup_info` - Startup information structure
307
    /// * `process_info` - Process information structure to fill
308
    ///
309
    /// # Returns
310
    ///
311
    /// Result indicating success or failure of the operation
312
    fn create_process_raw(
313
        &self,
314
        application: &str,
315
        command_line: windows::core::PWSTR,
316
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
317
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
318
    ) -> windows::core::Result<()>;
319
320
    /// Get window handle for process ID
321
    ///
322
    /// # Arguments
323
    ///
324
    /// * `process_id` - Process ID to find window for
325
    ///
326
    /// # Returns
327
    ///
328
    /// Window handle for the process
329
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND;
330
331
    /// Gets the console window handle.
332
    ///
333
    /// # Returns
334
    ///
335
    /// Handle to the console window
336
    fn get_console_window(&self) -> HWND;
337
338
    /// Gets the foreground window handle.
339
    ///
340
    /// # Returns
341
    ///
342
    /// Handle to the foreground window
343
    fn get_foreground_window(&self) -> HWND;
344
345
    /// Sets the foreground window.
346
    ///
347
    /// # Arguments
348
    ///
349
    /// * `hwnd` - Handle to the window to set as foreground
350
    ///
351
    /// # Returns
352
    ///
353
    /// Result indicating success or failure of the operation
354
    fn set_foreground_window(&self, hwnd: HWND) -> windows::core::Result<()>;
355
356
    /// Gets console mode for the specified handle.
357
    ///
358
    /// # Arguments
359
    ///
360
    /// * `handle` - Handle to the console input buffer
361
    ///
362
    /// # Returns
363
    ///
364
    /// Console mode or error
365
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE>;
366
367
    /// Sets console mode for the specified handle.
368
    ///
369
    /// # Arguments
370
    ///
371
    /// * `handle` - Handle to the console input buffer
372
    /// * `mode` - Console mode to set
373
    ///
374
    /// # Returns
375
    ///
376
    /// Result indicating success or failure of the operation
377
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()>;
378
379
    /// Gets the exit code of the specified process.
380
    ///
381
    /// # Arguments
382
    ///
383
    /// * `handle` - Handle to the process
384
    ///
385
    /// # Returns
386
    ///
387
    /// Exit code or error
388
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32>;
389
390
    /// Moves and resizes a window.
391
    ///
392
    /// # Arguments
393
    ///
394
    /// * `hwnd` - Handle to the window
395
    /// * `x` - New x position
396
    /// * `y` - New y position
397
    /// * `width` - New width
398
    /// * `height` - New height
399
    /// * `repaint` - Whether to repaint the window
400
    ///
401
    /// # Returns
402
    ///
403
    /// Result indicating success or failure of the operation
404
    fn move_window(
405
        &self,
406
        hwnd: HWND,
407
        x: i32,
408
        y: i32,
409
        width: i32,
410
        height: i32,
411
        repaint: bool,
412
    ) -> windows::core::Result<()>;
413
414
    /// Gets window placement information.
415
    ///
416
    /// # Arguments
417
    ///
418
    /// * `hwnd` - Handle to the window
419
    ///
420
    /// # Returns
421
    ///
422
    /// Window placement information or error
423
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT>;
424
425
    /// Shows a window in the specified state.
426
    ///
427
    /// # Arguments
428
    ///
429
    /// * `hwnd` - Handle to the window
430
    /// * `cmd_show` - Show command
431
    ///
432
    /// # Returns
433
    ///
434
    /// Result indicating success or failure of the operation
435
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool>;
436
437
    /// Focuses a window using UI Automation.
438
    ///
439
    /// # Arguments
440
    ///
441
    /// * `hwnd` - Handle to the window to focus
442
    ///
443
    /// # Returns
444
    ///
445
    /// Result indicating success or failure of the operation
446
    fn focus_window_with_automation(&self, hwnd: HWND) -> windows::core::Result<()>;
447
448
    /// Checks if a window handle is valid.
449
    ///
450
    /// # Arguments
451
    ///
452
    /// * `hwnd` - Handle to the window to check
453
    ///
454
    /// # Returns
455
    ///
456
    /// True if the window is valid, false otherwise
457
    fn is_window(&self, hwnd: HWND) -> bool;
458
459
    /// Opens a process with the specified access rights.
460
    ///
461
    /// # Arguments
462
    ///
463
    /// * `access` - Access rights for the process handle
464
    /// * `inherit` - Whether the handle can be inherited
465
    /// * `process_id` - Process ID to open
466
    ///
467
    /// # Returns
468
    ///
469
    /// Process handle or error
470
    fn open_process(
471
        &self,
472
        access: u32,
473
        inherit: bool,
474
        process_id: u32,
475
    ) -> windows::core::Result<HANDLE>;
476
477
    /// Initializes the COM library for use by the calling thread.
478
    ///
479
    /// # Arguments
480
    ///
481
    /// * `coinit` - Initialization options for the COM library
482
    ///
483
    /// # Returns
484
    ///
485
    /// Result indicating success or failure of the operation
486
    fn initialize_com_library(
487
        &self,
488
        coinit: windows::Win32::System::Com::COINIT,
489
    ) -> windows::core::Result<()>;
490
491
    /// Gets system metrics information.
492
    ///
493
    /// # Arguments
494
    ///
495
    /// * `index` - System metric index to retrieve
496
    ///
497
    /// # Returns
498
    ///
499
    /// The requested system metric value
500
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32;
501
502
    /// Sets the process DPI awareness.
503
    ///
504
    /// # Arguments
505
    ///
506
    /// * `value` - DPI awareness value to set
507
    ///
508
    /// # Returns
509
    ///
510
    /// Result indicating success or failure of the operation
511
    fn set_process_dpi_awareness(
512
        &self,
513
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
514
    ) -> windows::core::Result<()>;
515
}
516
517
#[cfg(test)]
518
impl Clone for MockWindowsApi {
519
0
    fn clone(&self) -> Self {
520
0
        return MockWindowsApi::new();
521
0
    }
522
}
523
524
/// Default implementation of WindowsApi that calls actual Windows APIs.
525
///
526
/// This implementation provides direct access to Windows system APIs and should
527
/// be used in production code. For testing, use the MockWindowsApi instead.
528
#[derive(Clone)]
529
pub struct DefaultWindowsApi;
530
531
impl WindowsApi for DefaultWindowsApi {
532
0
    fn set_console_title(&self, title: &str) -> windows::core::Result<()> {
533
0
        return unsafe { SetWindowTextW(GetConsoleWindow(), &HSTRING::from(title)) };
534
0
    }
535
536
0
    fn get_console_title(&self, buffer: &mut [u16]) -> i32 {
537
0
        return unsafe { GetWindowTextW(GetConsoleWindow(), buffer) };
538
0
    }
539
540
0
    fn get_os_version(&self) -> String {
541
0
        return os_info::get().version().to_string();
542
0
    }
543
544
0
    fn arrange_console(
545
0
        &self,
546
0
        x: i32,
547
0
        y: i32,
548
0
        width: i32,
549
0
        height: i32,
550
0
    ) -> windows::core::Result<()> {
551
0
        return unsafe { MoveWindow(GetConsoleWindow(), x, y, width, height, true) };
552
0
    }
553
554
0
    fn set_console_text_attribute(
555
0
        &self,
556
0
        attributes: CONSOLE_CHARACTER_ATTRIBUTES,
557
0
    ) -> windows::core::Result<()> {
558
0
        return unsafe { SetConsoleTextAttribute(self.get_stdout_handle()?, attributes) };
559
0
    }
560
561
0
    fn get_console_screen_buffer_info(&self) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO> {
562
0
        let mut buffer_info = CONSOLE_SCREEN_BUFFER_INFO::default();
563
0
        unsafe { GetConsoleScreenBufferInfo(self.get_stdout_handle()?, &mut buffer_info)? };
564
0
        return Ok(buffer_info);
565
0
    }
566
567
0
    fn fill_console_output_attribute(
568
0
        &self,
569
0
        attribute: u16,
570
0
        length: u32,
571
0
        coord: COORD,
572
0
    ) -> windows::core::Result<u32> {
573
0
        let mut number_written = 0u32;
574
        unsafe {
575
0
            FillConsoleOutputAttribute(
576
0
                self.get_stdout_handle()?,
577
0
                attribute,
578
0
                length,
579
0
                coord,
580
0
                &mut number_written,
581
0
            )?
582
        };
583
0
        return Ok(number_written);
584
0
    }
585
586
0
    fn scroll_console_screen_buffer(
587
0
        &self,
588
0
        scroll_rect: SMALL_RECT,
589
0
        scroll_target: COORD,
590
0
        fill_char: CHAR_INFO,
591
0
    ) -> windows::core::Result<()> {
592
        return unsafe {
593
0
            ScrollConsoleScreenBufferW(
594
0
                self.get_stdout_handle()?,
595
0
                &scroll_rect,
596
0
                None,
597
0
                scroll_target,
598
0
                &fill_char,
599
            )
600
        };
601
0
    }
602
603
0
    fn set_console_cursor_position(&self, position: COORD) -> windows::core::Result<()> {
604
0
        return unsafe { SetConsoleCursorPosition(self.get_stdout_handle()?, position) };
605
0
    }
606
607
0
    fn get_std_handle(&self, handle_type: STD_HANDLE) -> windows::core::Result<HANDLE> {
608
0
        return unsafe { GetStdHandle(handle_type) };
609
0
    }
610
611
0
    fn read_console_input(&self, buffer: &mut [INPUT_RECORD]) -> windows::core::Result<u32> {
612
0
        let mut number_read = 0u32;
613
        unsafe {
614
0
            ReadConsoleInputW(
615
0
                self.get_std_handle(STD_INPUT_HANDLE)?,
616
0
                buffer,
617
0
                &mut number_read,
618
0
            )?
619
        };
620
0
        return Ok(number_read);
621
0
    }
622
623
0
    fn set_console_border_color(&self, color: &COLORREF) -> windows::core::Result<()> {
624
        return unsafe {
625
0
            DwmSetWindowAttribute(
626
0
                GetConsoleWindow(),
627
                DWMWA_BORDER_COLOR,
628
0
                color as *const COLORREF as *const _,
629
0
                mem::size_of::<COLORREF>() as u32,
630
            )
631
        };
632
0
    }
633
634
0
    fn write_console_input(
635
0
        &self,
636
0
        buffer: &[INPUT_RECORD],
637
0
        number_written: &mut u32,
638
0
    ) -> windows::core::Result<()> {
639
        unsafe {
640
0
            windows::Win32::System::Console::WriteConsoleInputW(
641
0
                GetStdHandle(STD_INPUT_HANDLE)?,
642
0
                buffer,
643
0
                number_written,
644
0
            )?
645
        };
646
0
        return Ok(());
647
0
    }
648
649
0
    fn get_last_error(&self) -> u32 {
650
0
        return unsafe { windows::Win32::Foundation::GetLastError().0 };
651
0
    }
652
653
0
    fn generate_console_ctrl_event(
654
0
        &self,
655
0
        ctrl_event: u32,
656
0
        process_group_id: u32,
657
0
    ) -> windows::core::Result<()> {
658
        return unsafe {
659
0
            windows::Win32::System::Console::GenerateConsoleCtrlEvent(ctrl_event, process_group_id)
660
        };
661
0
    }
662
663
0
    fn get_stdout_handle(&self) -> windows::core::Result<HANDLE> {
664
0
        return self.get_std_handle(STD_OUTPUT_HANDLE);
665
0
    }
666
667
0
    fn get_console_screen_buffer_info_with_handle(
668
0
        &self,
669
0
        handle: HANDLE,
670
0
    ) -> windows::core::Result<CONSOLE_SCREEN_BUFFER_INFO> {
671
0
        let mut csbi = CONSOLE_SCREEN_BUFFER_INFO::default();
672
0
        unsafe { GetConsoleScreenBufferInfo(handle, &mut csbi) }?;
673
0
        return Ok(csbi);
674
0
    }
675
676
0
    fn get_window_handle_for_process(&self, process_id: u32) -> HWND {
677
        /// Data structure for window search callback
678
        struct WindowSearchData {
679
            /// The process ID we're searching for
680
            target_process_id: u32,
681
            /// Mutable reference to store the found window handle
682
            found_handle: *mut Option<HWND>,
683
        }
684
685
        /// Callback function for finding windows by process ID with proper handle capture
686
0
        unsafe extern "system" fn find_window_callback_with_capture(
687
0
            hwnd: HWND,
688
0
            lparam: LPARAM,
689
0
        ) -> BOOL {
690
0
            let search_data = &mut *(lparam.0 as *mut WindowSearchData);
691
0
            let mut window_process_id: u32 = 0;
692
0
            GetWindowThreadProcessId(hwnd, Some(&mut window_process_id));
693
694
0
            if search_data.target_process_id == window_process_id {
695
                // Store the found window handle
696
0
                *search_data.found_handle = Some(hwnd);
697
0
                return FALSE; // Stop enumeration
698
0
            }
699
0
            return TRUE; // Continue enumeration
700
0
        }
701
702
0
        let mut found_handle = None;
703
0
        let mut search_data = WindowSearchData {
704
0
            target_process_id: process_id,
705
0
            found_handle: &mut found_handle,
706
0
        };
707
708
        loop {
709
0
            let _ = unsafe {
710
0
                EnumWindows(
711
0
                    Some(find_window_callback_with_capture),
712
0
                    LPARAM(&mut search_data as *mut WindowSearchData as isize),
713
0
                )
714
0
            };
715
0
            if let Some(handle) = found_handle {
716
0
                return handle;
717
0
            }
718
        }
719
0
    }
720
721
0
    fn create_process_raw(
722
0
        &self,
723
0
        application: &str,
724
0
        command_line: windows::core::PWSTR,
725
0
        startup_info: &mut windows::Win32::System::Threading::STARTUPINFOW,
726
0
        process_info: &mut windows::Win32::System::Threading::PROCESS_INFORMATION,
727
0
    ) -> windows::core::Result<()> {
728
        return unsafe {
729
0
            CreateProcessW(
730
0
                &HSTRING::from(application),
731
0
                Some(command_line),
732
0
                Some(ptr::null_mut()),
733
0
                Some(ptr::null_mut()),
734
                false,
735
                CREATE_NEW_CONSOLE,
736
0
                Some(ptr::null_mut()),
737
0
                PCWSTR::null(),
738
0
                ptr::addr_of_mut!(*startup_info),
739
0
                ptr::addr_of_mut!(*process_info),
740
            )
741
        };
742
0
    }
743
744
0
    fn get_console_window(&self) -> HWND {
745
0
        return unsafe { GetConsoleWindow() };
746
0
    }
747
748
0
    fn get_foreground_window(&self) -> HWND {
749
0
        return unsafe { GetForegroundWindow() };
750
0
    }
751
752
0
    fn set_foreground_window(&self, hwnd: HWND) -> windows::core::Result<()> {
753
0
        let result = unsafe { SetForegroundWindow(hwnd) };
754
0
        if result.as_bool() {
755
0
            return Ok(());
756
        } else {
757
0
            return Err(windows::core::Error::from_win32());
758
        }
759
0
    }
760
761
0
    fn get_console_mode(&self, handle: HANDLE) -> windows::core::Result<CONSOLE_MODE> {
762
0
        let mut mode = CONSOLE_MODE(0u32);
763
0
        unsafe { GetConsoleMode(handle, &mut mode)? };
764
0
        return Ok(mode);
765
0
    }
766
767
0
    fn set_console_mode(&self, handle: HANDLE, mode: CONSOLE_MODE) -> windows::core::Result<()> {
768
0
        return unsafe { SetConsoleMode(handle, mode) };
769
0
    }
770
771
0
    fn get_exit_code(&self, handle: HANDLE) -> windows::core::Result<u32> {
772
0
        let mut exit_code: u32 = 0;
773
0
        unsafe { GetExitCodeProcess(handle, &mut exit_code)? };
774
0
        return Ok(exit_code);
775
0
    }
776
777
0
    fn move_window(
778
0
        &self,
779
0
        hwnd: HWND,
780
0
        x: i32,
781
0
        y: i32,
782
0
        width: i32,
783
0
        height: i32,
784
0
        repaint: bool,
785
0
    ) -> windows::core::Result<()> {
786
0
        return unsafe { MoveWindow(hwnd, x, y, width, height, repaint) };
787
0
    }
788
789
0
    fn get_window_placement(&self, hwnd: HWND) -> windows::core::Result<WINDOWPLACEMENT> {
790
0
        let mut placement: WINDOWPLACEMENT = WINDOWPLACEMENT {
791
0
            length: mem::size_of::<WINDOWPLACEMENT>() as u32,
792
0
            ..Default::default()
793
0
        };
794
0
        unsafe { GetWindowPlacement(hwnd, &mut placement)? };
795
0
        return Ok(placement);
796
0
    }
797
798
0
    fn show_window(&self, hwnd: HWND, cmd_show: SHOW_WINDOW_CMD) -> windows::core::Result<bool> {
799
0
        let result = unsafe { ShowWindow(hwnd, cmd_show) };
800
0
        return Ok(result.as_bool());
801
0
    }
802
803
0
    fn focus_window_with_automation(&self, hwnd: HWND) -> windows::core::Result<()> {
804
0
        let automation: IUIAutomation =
805
0
            unsafe { CoCreateInstance(&CUIAutomation, None, CLSCTX_ALL)? };
806
0
        let window = unsafe { automation.ElementFromHandle(hwnd)? };
807
0
        unsafe { window.SetFocus()? };
808
0
        return Ok(());
809
0
    }
810
811
0
    fn is_window(&self, hwnd: HWND) -> bool {
812
0
        return unsafe { windows::Win32::UI::WindowsAndMessaging::IsWindow(Some(hwnd)).as_bool() };
813
0
    }
814
815
0
    fn open_process(
816
0
        &self,
817
0
        access: u32,
818
0
        inherit: bool,
819
0
        process_id: u32,
820
0
    ) -> windows::core::Result<HANDLE> {
821
0
        return unsafe { OpenProcess(PROCESS_ACCESS_RIGHTS(access), inherit, process_id) };
822
0
    }
823
824
0
    fn initialize_com_library(
825
0
        &self,
826
0
        coinit: windows::Win32::System::Com::COINIT,
827
0
    ) -> windows::core::Result<()> {
828
0
        let result = unsafe { windows::Win32::System::Com::CoInitializeEx(None, coinit) };
829
0
        if result.is_ok() {
830
0
            return Ok(());
831
        } else {
832
0
            return Err(windows::core::Error::from(result));
833
        }
834
0
    }
835
836
0
    fn get_system_metrics(&self, index: SYSTEM_METRICS_INDEX) -> i32 {
837
0
        return unsafe { windows::Win32::UI::WindowsAndMessaging::GetSystemMetrics(index) };
838
0
    }
839
840
0
    fn set_process_dpi_awareness(
841
0
        &self,
842
0
        value: windows::Win32::UI::HiDpi::PROCESS_DPI_AWARENESS,
843
0
    ) -> windows::core::Result<()> {
844
0
        return unsafe { windows::Win32::UI::HiDpi::SetProcessDpiAwareness(value) };
845
0
    }
846
}
847
848
/// u16 representation of a [KEY_EVENT][1].
849
///
850
/// For some reason the public [KEY_EVENT][1] constant is a u32
851
/// while the [INPUT_RECORD][2].`EventType` is u16...
852
///
853
/// [1]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/constant.KEY_EVENT.html
854
/// [2]: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/Console/struct.INPUT_RECORD.html
855
pub const KEY_EVENT: u16 = KEY_EVENT_U32 as u16;
856
857
/// Build command line string for Windows process creation
858
///
859
/// # Arguments
860
///
861
/// * `application` - Application name including file extension
862
/// * `args` - List of arguments to the application
863
///
864
/// # Returns
865
///
866
/// UTF-16 encoded command line with proper quoting
867
///
868
/// # Examples
869
///
870
/// ```
871
/// use csshw_lib::utils::windows::build_command_line;
872
///
873
/// let cmd_line = build_command_line("cmd.exe", &["arg1".to_string(), "arg2".to_string()]);
874
/// // Returns UTF-16 encoded: "cmd.exe" "arg1" "arg2"\0
875
/// ```
876
0
pub fn build_command_line(application: &str, args: &[String]) -> Vec<u16> {
877
0
    let mut cmd: Vec<u16> = Vec::new();
878
0
    cmd.push(b'"' as u16);
879
0
    cmd.extend(OsString::from(application).encode_wide());
880
0
    cmd.push(b'"' as u16);
881
882
0
    for arg in args {
883
0
        cmd.push(' ' as u16);
884
0
        cmd.push(b'"' as u16);
885
0
        cmd.extend(OsString::from(arg).encode_wide());
886
0
        cmd.push(b'"' as u16);
887
0
    }
888
0
    cmd.push(0); // add null terminator
889
890
0
    return cmd;
891
0
}
892
893
/// Sets the back- and foreground color of the current console window using the provided API.
894
///
895
/// # Arguments
896
///
897
/// * `api` - The Windows API implementation to use.
898
/// * `color` - The color value describing the back- and foreground color.
899
///
900
/// # Examples
901
///
902
/// ```no_run
903
/// use csshw_lib::utils::windows::{set_console_color, DefaultWindowsApi};
904
/// use windows::Win32::System::Console::CONSOLE_CHARACTER_ATTRIBUTES;
905
///
906
/// let api = DefaultWindowsApi;
907
/// set_console_color(&api, CONSOLE_CHARACTER_ATTRIBUTES(0x0F));
908
/// ```
909
0
pub fn set_console_color(api: &dyn WindowsApi, color: CONSOLE_CHARACTER_ATTRIBUTES) {
910
0
    api.set_console_text_attribute(color).unwrap();
911
0
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
912
0
    for y in 0..buffer_info.dwSize.Y {
913
0
        api.fill_console_output_attribute(
914
0
            color.0,
915
0
            buffer_info.dwSize.X.try_into().unwrap(),
916
0
            COORD { X: 0, Y: y },
917
0
        )
918
0
        .unwrap();
919
0
    }
920
0
}
921
922
/// Empties the console screen output buffer of the current console window using the provided API.
923
///
924
/// # Arguments
925
///
926
/// * `api` - The Windows API implementation to use.
927
///
928
/// # Examples
929
///
930
/// ```no_run
931
/// use csshw_lib::utils::windows::{clear_screen, DefaultWindowsApi};
932
///
933
/// let api = DefaultWindowsApi;
934
/// clear_screen(&api);
935
/// ```
936
0
pub fn clear_screen(api: &dyn WindowsApi) {
937
0
    let buffer_info = api.get_console_screen_buffer_info().unwrap();
938
0
    let scroll_rect = SMALL_RECT {
939
0
        Left: 0,
940
0
        Top: 0,
941
0
        Right: buffer_info.dwSize.X,
942
0
        Bottom: buffer_info.dwSize.Y,
943
0
    };
944
0
    let scroll_target = COORD {
945
0
        X: buffer_info.dwSize.X,
946
0
        Y: 0 - buffer_info.dwSize.Y,
947
0
    };
948
0
    let mut char_info = CHAR_INFO::default();
949
0
    char_info.Char.UnicodeChar = ' ' as u16;
950
0
    char_info.Attributes = buffer_info.wAttributes.0;
951
952
0
    api.scroll_console_screen_buffer(scroll_rect, scroll_target, char_info)
953
0
        .unwrap();
954
955
0
    let cursor_position = COORD { X: 0, Y: 0 };
956
0
    api.set_console_cursor_position(cursor_position).unwrap();
957
0
}
958
959
/// Sets the border color of the current console window using the provided APIs.
960
///
961
/// Windows10 does not support this.
962
///
963
/// # Arguments
964
///
965
/// * `api` - The Windows API implementation;
966
/// * `color` - RGB [COLORREF][1] to set as border color.
967
///
968
/// # Examples
969
///
970
/// ```no_run
971
/// use csshw_lib::utils::windows::{set_console_border_color, DefaultWindowsApi};
972
/// use windows::Win32::Foundation::COLORREF;
973
///
974
/// set_console_border_color(&DefaultWindowsApi, COLORREF(0x001A2B3C));
975
/// ```
976
///
977
/// [1]: https://learn.microsoft.com/en-us/windows/win32/gdi/colorref
978
0
pub fn set_console_border_color(api: &dyn WindowsApi, color: COLORREF) {
979
0
    if !is_windows_10(api) {
980
0
        api.set_console_border_color(&color).unwrap();
981
0
    }
982
0
}
983
984
/// Converts a UTF-16 buffer to a Rust String, filtering out null characters.
985
///
986
/// # Arguments
987
///
988
/// * `buffer` - The UTF-16 buffer to convert.
989
///
990
/// # Returns
991
///
992
/// The converted string.
993
///
994
/// # Examples
995
///
996
/// ```
997
/// use csshw_lib::utils::windows::utf16_buffer_to_string;
998
///
999
/// let utf16_data = vec![72, 101, 108, 108, 111, 0]; // "Hello" + null terminator
1000
/// let result = utf16_buffer_to_string(&utf16_data);
1001
/// assert_eq!(result, "Hello");
1002
/// ```
1003
0
pub fn utf16_buffer_to_string(buffer: &[u16]) -> String {
1004
0
    let vec: Vec<u16> = buffer
1005
0
        .iter()
1006
0
        .copied()
1007
0
        .filter(|val| return *val != 0u16)
1008
0
        .collect();
1009
0
    return String::from_utf16(&vec).unwrap_or_else(|err| {
1010
0
        error!("{}", err);
1011
0
        panic!("Failed to convert UTF-16 buffer to string, invalid utf16")
1012
    });
1013
0
}
1014
1015
/// Returns the title of the current console window using the provided API.
1016
///
1017
/// # Arguments
1018
///
1019
/// * `api` - The Windows API implementation to use.
1020
///
1021
/// # Returns
1022
///
1023
/// The title of the current console window.
1024
///
1025
/// # Examples
1026
///
1027
/// ```no_run
1028
/// use csshw_lib::utils::windows::{get_console_title, DefaultWindowsApi};
1029
///
1030
/// let title = get_console_title(&DefaultWindowsApi);
1031
/// println!("Console title: {}", title);
1032
/// ```
1033
0
pub fn get_console_title(api: &dyn WindowsApi) -> String {
1034
0
    let mut title: [u16; MAX_WINDOW_TITLE_LENGTH] = [0; MAX_WINDOW_TITLE_LENGTH];
1035
0
    api.get_console_title(&mut title);
1036
0
    return utf16_buffer_to_string(&title);
1037
0
}
1038
1039
/// Returns a [HANDLE] to the requested [STD_HANDLE] of the current process.
1040
///
1041
/// # Arguments
1042
///
1043
/// * `nstdhandle` - The standard handle to retrieve.
1044
///                  Either [STD_INPUT_HANDLE] or [STD_OUTPUT_HANDLE].
1045
///
1046
/// # Returns
1047
///
1048
/// The [HANDLE] to the requested [STD_HANDLE].
1049
0
fn get_std_handle(nstdhandle: STD_HANDLE) -> HANDLE {
1050
    return unsafe {
1051
0
        GetStdHandle(nstdhandle)
1052
0
            .unwrap_or_else(|_| panic!("Failed to retrieve standard handle: {nstdhandle:?}"))
1053
    };
1054
0
}
1055
1056
/// Returns a [HANDLE] to the [STD_INPUT_HANDLE] of the current process.
1057
///
1058
/// # Returns
1059
///
1060
/// Handle to the standard input of the current process.
1061
///
1062
/// # Examples
1063
///
1064
/// ```no_run
1065
/// use csshw_lib::utils::windows::get_console_input_buffer;
1066
///
1067
/// let input_handle = get_console_input_buffer();
1068
/// ```
1069
0
pub fn get_console_input_buffer() -> HANDLE {
1070
0
    return get_std_handle(STD_INPUT_HANDLE);
1071
0
}
1072
1073
/// Returns a [HANDLE] to the [STD_OUTPUT_HANDLE] of the current process.
1074
///
1075
/// # Returns
1076
///
1077
/// Handle to the standard output of the current process.
1078
///
1079
/// # Examples
1080
///
1081
/// ```no_run
1082
/// use csshw_lib::utils::windows::get_console_output_buffer;
1083
///
1084
/// let output_handle = get_console_output_buffer();
1085
/// ```
1086
0
pub fn get_console_output_buffer() -> HANDLE {
1087
0
    return get_std_handle(STD_OUTPUT_HANDLE);
1088
0
}
1089
1090
/// Returns a single [INPUT_RECORD] read from the current process stdinput using the provided API.
1091
///
1092
/// Blocks until 1 record was read.
1093
///
1094
/// # Arguments
1095
///
1096
/// * `api` - The Windows API implementation to use.
1097
///
1098
/// # Returns
1099
///
1100
/// A single INPUT_RECORD that was read.
1101
///
1102
/// # Examples
1103
///
1104
/// ```no_run
1105
/// use csshw_lib::utils::windows::{read_console_input, DefaultWindowsApi};
1106
///
1107
/// let api = DefaultWindowsApi;
1108
/// let input_record = read_console_input(&api);
1109
/// ```
1110
0
pub fn read_console_input(api: &dyn WindowsApi) -> INPUT_RECORD {
1111
    const NB_EVENTS: usize = 1;
1112
0
    let mut input_buffer: [INPUT_RECORD; NB_EVENTS] = [INPUT_RECORD::default(); NB_EVENTS];
1113
    loop {
1114
0
        let number_of_events_read = api
1115
0
            .read_console_input(&mut input_buffer)
1116
0
            .expect("Failed to read console input");
1117
0
        if number_of_events_read == NB_EVENTS as u32 {
1118
0
            break;
1119
0
        }
1120
    }
1121
0
    return input_buffer[0];
1122
0
}
1123
1124
/// Returns a single [INPUT_RECORD_0] where `EventType` == [`KEY_EVENT`] using the provided API.
1125
///
1126
/// Blocks until 1 key event record was read.
1127
///
1128
/// # Arguments
1129
///
1130
/// * `api` - The Windows API implementation to use.
1131
///
1132
/// # Returns
1133
///
1134
/// A single INPUT_RECORD_0 with EventType == KEY_EVENT.
1135
///
1136
/// # Examples
1137
///
1138
/// ```no_run
1139
/// use csshw_lib::utils::windows::{read_keyboard_input, DefaultWindowsApi};
1140
///
1141
/// let api = DefaultWindowsApi;
1142
/// let key_event = read_keyboard_input(&api);
1143
/// ```
1144
0
pub fn read_keyboard_input(api: &dyn WindowsApi) -> INPUT_RECORD_0 {
1145
    loop {
1146
0
        let input_record = read_console_input(api);
1147
0
        match input_record.EventType {
1148
            KEY_EVENT => {
1149
0
                return input_record.Event;
1150
            }
1151
            _ => {
1152
0
                continue;
1153
            }
1154
        }
1155
    }
1156
0
}
1157
1158
/// Changes size and position of the current console window using the provided API.
1159
///
1160
/// # Arguments
1161
///
1162
/// * `api` - The Windows API implementation to use.
1163
/// * `x`       - The x coordinate to move the window to.
1164
///               From the top left corner of the screen.
1165
/// * `y`       - The y coordinate to move the window to.
1166
///               From the top left corner of the screen.
1167
/// * `width`   - The width in pixels to resize the window to.
1168
///               In logical scaling.
1169
/// * `height`  - The height in pixels to resize the window to.
1170
///               In logical scaling.
1171
///
1172
/// # Examples
1173
///
1174
/// ```no_run
1175
/// use csshw_lib::utils::windows::{arrange_console, DefaultWindowsApi};
1176
///
1177
/// let api = DefaultWindowsApi;
1178
/// arrange_console(&api, 100, 100, 800, 600);
1179
/// ```
1180
0
pub fn arrange_console(api: &dyn WindowsApi, x: i32, y: i32, width: i32, height: i32) {
1181
    // FIXME: sometimes a daemon or client console isn't being arrange correctly
1182
    // when this simply retrying doesn't solve the issue. Maybe it has something to do
1183
    // with DPI awareness => https://docs.rs/embed-manifest/latest/embed_manifest/
1184
0
    api.arrange_console(x, y, width, height).unwrap();
1185
0
}
1186
1187
/// Detects if the current windows installation is Windows 10 or not using the provided API.
1188
///
1189
/// Uses the os version, Windows 10 is < `10._.22000`. Windows 11 started with build 22000.
1190
///
1191
/// # Arguments
1192
///
1193
/// * `api` - The Windows API implementation to use.
1194
///
1195
/// # Returns
1196
///
1197
/// Whether the current windows installation is Windows 10 or not.
1198
///
1199
/// # Examples
1200
///
1201
/// ```no_run
1202
/// use csshw_lib::utils::windows::{is_windows_10, DefaultWindowsApi};
1203
///
1204
/// if is_windows_10(&DefaultWindowsApi) {
1205
///     println!("Running on Windows 10");
1206
/// } else {
1207
///     println!("Running on Windows 11 or newer");
1208
/// }
1209
/// ```
1210
0
pub fn is_windows_10(api: &dyn WindowsApi) -> bool {
1211
0
    let version = api.get_os_version();
1212
0
    let mut iter = version.split('.');
1213
0
    let (major, _, build) = (
1214
0
        iter.next().unwrap().parse::<usize>().unwrap(),
1215
0
        iter.next().unwrap().parse::<usize>().unwrap(),
1216
0
        iter.next().unwrap().parse::<usize>().unwrap(),
1217
0
    );
1218
0
    return major < 10 || (major == 10 && build < 22000);
1219
0
}